import { Node, Layers } from 'cc';
import { HUDController } from '../hud/HUDController';
import { StateOverlay } from '../debug/StateOverlay';
import { EventSpy } from '../debug/EventSpy';
import MiniMap from '../hud/MiniMap';
import { PlayerAvatar } from '../gameplay/PlayerAvatar';
import { EnemyDummy } from '../gameplay/EnemyDummy';
import ChunkManager from './ChunkManager';

type LayerCounter = { fixed: number };

export interface WorldBootstrapResult {
  world: Node;
  ui: Node;
}

export function bootstrapWorld(canvas: Node): WorldBootstrapResult {
  console.log('[WORLD] bootstrap...');
  try {
    const world = ensureChild(canvas, 'World', Layers.Enum.DEFAULT);
    const ui = ensureChild(canvas, 'UI', Layers.Enum.UI_2D);
    const counter: LayerCounter = { fixed: 0 };
    ensureUiAnchors(canvas, ui, counter);
    ensureWorldEntities(canvas, world);
    console.debug(`[LAYERS] world=DEFAULT ui=UI_2D hudFixed=${counter.fixed}`);
    return { world, ui };
  } catch (err) {
    console.error('[WORLD-ERROR]', err);
    throw err;
  }
}

function ensureChild(canvas: Node, name: string, layer: Layers.Enum): Node {
  let node = canvas.getChildByName(name);
  if (!node) {
    node = new Node(name);
    canvas.addChild(node);
  }
  node.layer = layer;
  return node;
}

function ensureUiAnchors(canvas: Node, ui: Node, counter: LayerCounter) {
  const uiComponents = [
    ...canvas.getComponentsInChildren(HUDController),
    ...canvas.getComponentsInChildren(StateOverlay),
    ...canvas.getComponentsInChildren(EventSpy),
    ...canvas.getComponentsInChildren(MiniMap),
  ];
  for (const comp of uiComponents) {
    const node = comp.node;
    if (!node) continue;
    if (node.parent !== ui) {
      node.removeFromParent();
      ui.addChild(node);
      counter.fixed += 1;
    }
    if (node.layer !== Layers.Enum.UI_2D) {
      node.layer = Layers.Enum.UI_2D;
      counter.fixed += 1;
    }
  }
}

function ensureWorldEntities(canvas: Node, world: Node) {
  const movers = new Set<Node>();

  for (const comp of canvas.getComponentsInChildren(PlayerAvatar)) {
    if (comp.node) movers.add(comp.node);
  }
  for (const comp of canvas.getComponentsInChildren(EnemyDummy)) {
    if (comp.node) movers.add(comp.node);
  }
  for (const comp of canvas.getComponentsInChildren(ChunkManager)) {
    if (comp.node && comp.node !== world) {
      movers.add(comp.node);
    }
  }

  const chunkNodes: Node[] = [];
  collectNodes(canvas, (node) => node.name.startsWith('TileChunk_'), chunkNodes);
  for (const node of chunkNodes) {
    movers.add(node);
  }

  const playerNode = canvas.getChildByName('Player') ?? world.getChildByName('Player');
  if (playerNode) movers.add(playerNode);

  const enemyNodes: Node[] = [];
  collectNodes(canvas, (node) => node.name.startsWith('Enemy_'), enemyNodes);
  for (const node of enemyNodes) {
    movers.add(node);
  }

  for (const node of movers) {
    if (!node || node === world) continue;
    if (node.parent !== world && node.parent) {
      node.removeFromParent();
      world.addChild(node);
    } else if (!node.parent) {
      world.addChild(node);
    }
    node.layer = Layers.Enum.DEFAULT;
  }
}

function collectNodes(root: Node, predicate: (node: Node) => boolean, out: Node[]) {
  for (const child of root.children) {
    if (predicate(child)) {
      out.push(child);
    }
    collectNodes(child, predicate, out);
  }
}

export default {
  bootstrapWorld,
};
